﻿using CookComputing.XmlRpc;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data.Linq;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace HomeWP
{
    public class HomeMaticData : INotifyPropertyChanged, IDisposable
    {
        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        // Used to notify Silverlight that a property has changed.
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion


        [XmlRpcUrl(HomematicRpc.UrlCCU)]
        private class HomematicRpc : XmlRpcClientProtocol
        {
            public const string UrlCCU = "http://192.168.2.15:2001";

            [XmlRpcBegin("listDevices")]
            public IAsyncResult ListDevices(AsyncCallback acb)
            {
                this.ResponseEvent += new XmlRpcResponseEventHandler(Homematic_ResponseEvent);
                return this.BeginInvoke(MethodBase.GetCurrentMethod(), new object[] { }, acb, null);
            }

            public string GetStringFromStream(Stream stream)
            {
                // Create a stream reader.
                using (StreamReader reader = new StreamReader(stream))
                {
                    // Just read to the end.
                    return reader.ReadToEnd();
                }
            }

            void Homematic_ResponseEvent(object sender, XmlRpcResponseEventArgs args)
            {
                if (this.Finished != null)
                    this.Finished.Invoke(sender, new EventArgs());
            }

            public event EventHandler Finished;


            [XmlRpcEnd]
            public XmlRpcStruct[] EndListDevicesGet(IAsyncResult iasr)
            {
                XmlRpcStruct[] ret = (XmlRpcStruct[])this.EndInvoke(iasr);
                return ret;
            }

            [XmlRpcEnd]
            public object EndGetValue(IAsyncResult iasr)
            {
                return this.EndInvoke(iasr);
            }

            [XmlRpcBegin("getValue")]
            internal IAsyncResult getValue(string address, string valueKey, AsyncCallback acb)
            {
                return this.BeginInvoke(MethodBase.GetCurrentMethod(), new object[] { address, valueKey }, acb, null);
            }

            [XmlRpcBegin("setValue")]
            internal IAsyncResult setValue(String address, String valueKey, ValueType value, AsyncCallback acb)
            {
                return this.BeginInvoke(MethodBase.GetCurrentMethod(), new object[] { address, valueKey, value }, acb, null);
            }


            public void SetValue(string Address, ValueType Value)
            {
                setValue(Address, "STATE", Value, asr =>
                {
                    ((App)App.Current).RootFrame.Dispatcher.BeginInvoke(delegate()
                    {
                        try
                        {
                            var posts = EndGetValue(asr);
                        }
                        catch { }
                    });

                });
            }
        }

        const string HMQuery = "string s_device;string s_channel;object o_device;object o_channel;string s_temp;var rooms = \"\";var functions = \"\";string id;" +
           "foreach(s_device, dom.GetObject(ID_DEVICES).EnumUsedIDs()){" +
           "var o_device = dom.GetObject(s_device); WriteLine(o_device.Address()+\":\"+o_device.Name());" +
           "foreach(s_channel, o_device.Channels().EnumUsedIDs()){" +
           "o_channel = dom.GetObject(s_channel);s_temp = \":\" # o_channel.Address();rooms = \"\";" +
           "foreach(id, o_channel.ChnRoom()){" +
           "var room = dom.GetObject(id);rooms = rooms # room.Name() # \"!\";}" +
           "functions = \"\";" +
           "foreach(id, o_channel.ChnFunction()){" +
           "var function = dom.GetObject(id);functions = functions # function.Name() # \"!\";}" +
           "WriteLine(s_temp # \";\" # o_channel.Name() # \";\" # rooms  # \";\" # functions);}}";

        const string HMQueryVariables = "string svId;foreach(svId, dom.GetObject(ID_SYSTEM_VARIABLES).EnumIDs()){" +
            "var oSV     = dom.GetObject(svId);" +
            "Write(oSV.ID());" +
            "Write(\"|\" #oSV.Timestamp().ToInteger());" +
            "Write(\"|\" #oSV.Name());" +
            "Write(\"|\" #oSV.Value());" +
            "Write(\"|\" #oSV.Type());" +
            "Write(\"|\" #oSV.ValueSubType());" +
            "Write(\"|\" #oSV.ValueUnit());" +
            "Write(\"|\" #oSV.ValueMin());" +
            "Write(\"|\" #oSV.ValueMax());" +
            "Write(\"|\" #oSV.Visible());" +
            "Write(\"|\" #oSV.DPArchive());" +
            "WriteLine(\"|\" #oSV.ValueList());}";

        private HomematicRpc proxy;

        private enum LoadProcesses { LoadWired, LoadRF, LoadDevices, LoadVariables };
        private HashSet<LoadProcesses> LoadProcessesFinished = new HashSet<LoadProcesses>();


        // Data context for the local database
        public HMDataContext DataContext;
        public string CCUAddress;

        public delegate void LoadingFinishedDelegate();
        // Das Event-Objekt ist vom Typ dieses Delegaten
        public event LoadingFinishedDelegate LoadingFinished;

        private void OnLoadingFinished()
        {
            // Prüft ob das Event überhaupt einen Abonnenten hat.
            if (LoadingFinished != null)
                LoadingFinished();
        }


        public class LoadingErrorEventArgs : EventArgs
        {
            public string Message { get; set; }
        }
        public delegate void LoadingErrorDelegate(object sender, EventArgs e);
        // Das Event-Objekt ist vom Typ dieses Delegaten
        public event LoadingErrorDelegate LoadingError;

        private void OnLoadingError(string aMessage)
        {
            // Prüft ob das Event überhaupt einen Abonnenten hat.
            if (LoadingError != null)
                LoadingError(this, new LoadingErrorEventArgs() { Message = aMessage });
        }


        public delegate void FavoriteChangedDelegate();
        // Das Event-Objekt ist vom Typ dieses Delegaten
        public event FavoriteChangedDelegate FavoriteChanged;

        private void OnFavoriteAdded()
        {
            // Prüft ob das Event überhaupt einen Abonnenten hat.
            if (FavoriteChanged != null)
                FavoriteChanged();
        }

        // Define an observable collection property that controls can bind to.
        private ObservableCollection<Device> _hMDevices;
        private ObservableCollection<SystemVariable> _hMVariables;
        public ObservableCollection<Device> HMDevices
        {
            get
            {
                return _hMDevices;
            }
            set
            {
                if (_hMDevices != value)
                {
                    _hMDevices = value;
                    NotifyPropertyChanged("HMDevices");
                }
            }
        }

        public ObservableCollection<SystemVariable> HMVariables
        {
            get
            {
                return _hMVariables;
            }
            set
            {
                if (_hMVariables != value)
                {
                    _hMVariables = value;
                    NotifyPropertyChanged("HMVariable");
                }
            }
        }

        public void Init()
        {
            // Create the database if it does not exist.
            using (HMDataContext db = new HMDataContext(HMDataContext.DBConnectionString))
            {
                if (db.DatabaseExists() == false)
                {
                    //Create the database
                    db.CreateDatabase();
                }
            }

            // Connect to the database and instantiate data context.
            DataContext = new HMDataContext(HMDataContext.DBConnectionString);

            // Define the query to gather all of the to-do items.
            var HMItemsInDB = from Device todo in DataContext.HMDevices select todo;
            // Execute the query and place the results into a collection.
            HMDevices = new ObservableCollection<Device>(from Device oItem in DataContext.HMDevices select oItem);
            HMVariables = new ObservableCollection<SystemVariable>(from SystemVariable oItem in DataContext.HMVariables_ select oItem);
        }

        public bool CheckHasHMData()
        {
            return ((DataContext.HMRoom.Count() > 0) || (DataContext.HMDevices.Count() > 0));
        }

        public void AddFavorit(string Address)
        {
            if (DataContext.HMFavorite.Count(t => t.Address == Address)==0) {
                Favorite f = new Favorite();
                f.Address = Address;
                DataContext.HMFavorite.InsertOnSubmit(f);
                DataContext.SubmitChanges();
                FavoriteChanged();
            }

        }


        public void DeleteFavorit(string Address)
        {
            if (DataContext.HMFavorite.Count(t => t.Address == Address) > 0)
            {
                DataContext.HMFavorite.DeleteOnSubmit(DataContext.HMFavorite.Where(t => t.Address == Address).First());
                DataContext.SubmitChanges();
                FavoriteChanged();
            }

        }

        public bool IsFavorit(string Address)
        {
            return (DataContext.HMFavorite.Count(t => t.Address == Address) > 0);
        }


        private void LoadFromURL(LoadProcesses Process, CCUSettings CCUConf, string aUrl)
        {
            proxy = new HomematicRpc();
            proxy.Url = aUrl;

            #region     Get BidCos RF Devices
            proxy.ListDevices(asr =>
            {
                ((App)App.Current).RootFrame.Dispatcher.BeginInvoke(delegate()
                {
                    try
                    {
                        var posts = proxy.EndListDevicesGet(asr);
                        foreach (var post in posts)
                        {
                            Device oResult = new Device();

                            if (Process == LoadProcesses.LoadWired)
                                oResult.Bus = "Wired";
                            else
                                oResult.Bus = "RF";

                            string sType = post["TYPE"].ToString();
                            oResult.Address = post["ADDRESS"].ToString();

                            try
                            {
                                if (post.ContainsKey("CHILDREN"))
                                {
                                    foreach (string sChild in post["CHILDREN"] as string[])
                                    {
                                        Children oChild = new Children();
                                        oChild.ParentAddress = oResult.Address;
                                        oChild.ChildAddress = sChild;
                                        oResult.Childrens.Add(oChild);
                                    }

                                    if (oResult.Childrens == null)
                                    {
                                        oResult.Childrens.ToString();
                                    }
                                }

                            }
                            catch { }



                            if (post.ContainsKey("DIRECTION"))
                                oResult.Direction = post["DIRECTION"] as int?;
                            if (post.ContainsKey("FIRMWARE"))
                                oResult.Firmware = post["FIRMWARE"].ToString();
                            if (post.ContainsKey("PARENT"))
                                oResult.Parent = post["PARENT"].ToString();
                            if (post.ContainsKey("PARENT_TYPE"))
                                oResult.ParentType = post["PARENT_TYPE"].ToString();
                            if (post.ContainsKey("TYPE"))
                                oResult.Type = post["TYPE"].ToString();

                            try
                            {
                                oResult.Modified = DateTime.Now;
                                // Add a to-do item to the observable collection.
                                if (HMDevices.Count(t => t.Address == oResult.Address) == 0)
                                {
                                    HMDevices.Add(oResult);

                                    // Add a to-do item to the local database.
                                    DataContext.HMDevices.InsertOnSubmit(oResult);
                                    DataContext.SubmitChanges();
                                }
                                else
                                {
                                    HMDevices.Single(t => t.Address == oResult.Address).Modified = DateTime.Now;
                                    //DataContext.HMDevices.Single(t => t.Address == oResult.Address).Modified = DateTime.Now;
                                    DataContext.SubmitChanges();
                                }
                            }
                            catch (Exception ex)
                            {
                                ex.Message.ToString();
                            }


                        }// end for each post
                        AProccessHasFinished(Process);

                    }
                    catch
                    {
                        string msg = "Die Homematic-Zentrale (CCU) ist nicht erreichbar... !\r(Hinweis: Port 2000,2001 und 8181 werden benötigt).";
                        OnLoadingError(msg);
                    }
                });
            });

            #endregion
        }

        public void Load()
        {
            LoadProcessesFinished.Clear();

            if (DataContext.DatabaseExists())
            {
                DataContext.SubmitChanges();
                // Delete all Data
                foreach (var item in DataContext.HMFunction.ToList())
                    DataContext.HMFunction.DeleteOnSubmit(item);
                foreach (var item in DataContext.HMChildren.ToList())
                    DataContext.HMChildren.DeleteOnSubmit(item);
                foreach (var item in DataContext.HMRoom.ToList())
                    DataContext.HMRoom.DeleteOnSubmit(item);
                foreach (var item in DataContext.HMParamSet.ToList())
                    DataContext.HMParamSet.DeleteOnSubmit(item);
                foreach (var item in DataContext.HMVariables_.ToList())
                    DataContext.HMVariables_.DeleteOnSubmit(item);
                foreach (var item in DataContext.HMDevices.ToList())
                    DataContext.HMDevices.DeleteOnSubmit(item);

                DataContext.HMCCUSettings.DeleteAllOnSubmit(DataContext.HMCCUSettings.ToList());

                DataContext.SubmitChanges();
                //DataContext.DeleteDatabase();
            }
            if (!DataContext.DatabaseExists())
            {
                DataContext.CreateDatabase();
                DataContext.SubmitChanges();
            }


            if (DataContext.HMCCUSettings.Count(t => t.ConfigID == "TEST") == 0)
            {
                CCUSettings CCUSet = new CCUSettings();
                CCUSet.ConfigID = "TEST";
                CCUSet.CCU = CCUAddress;
                CCUSet.Port = 2000;
                CCUSet.Modified = DateTime.Now;
                DataContext.HMCCUSettings.InsertOnSubmit(CCUSet);
                DataContext.SubmitChanges();
            }

            CCUSettings CCUConf = DataContext.HMCCUSettings.Single(t => t.ConfigID == "TEST");

            LoadFromURL(LoadProcesses.LoadRF, CCUConf, "http://" + CCUConf.CCU + ":" + (CCUConf.Port + 1).ToString());
            LoadFromURL(LoadProcesses.LoadWired, CCUConf, "http://" + CCUConf.CCU + ":" + CCUConf.Port.ToString());


        }


        void oWeb_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
        {
            try
            {
                string Res = e.Result;

                byte[] bytes = System.Text.Encoding.UTF8.GetBytes(Res);

                string ResAsci = System.Text.Encoding.Unicode.GetString(bytes, 0, bytes.Length);


                byte[] bytes2 = System.Text.Encoding.Unicode.GetBytes(Res);

                string ResAsci2 = System.Text.Encoding.UTF8.GetString(bytes2, 0, bytes.Length);


                foreach (string sLine in Res.Split(new string[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries))
                {
                    try
                    {
                        Boolean bChanel = false;
                        string sName = "";
                        string sName2 = "";
                        string sAddress = "";
                        List<string> lRooms = new List<string>();
                        List<string> lFunctions = new List<string>();

                        if (sLine.StartsWith(":"))
                        {
                            bChanel = true;
                        }

                        if (!bChanel)
                        {
                            sName = sLine.Split(':')[1];
                            sAddress = sLine.Split(':')[0];
                        }
                        else
                        {
                            sName = sLine.Split(';')[1].Split(':')[0];
                            try
                            {
                                var snSplit = sLine.Split(';')[1].Split(':');
                                if (snSplit.Length > 2)
                                {
                                    sName2 = snSplit[1];
                                    int n;
                                    if (int.TryParse(sName2, out n))
                                        sName2 = null;
                                }
                            }
                            catch
                            { }

                            sAddress = sLine.Split(':')[1] + ":" + sLine.Split(':')[2].Split(';')[0];

                            //Räume abfüllen....
                            string aRooms = sLine.Split(';')[2];
                            foreach (string sRoom in aRooms.Split('!'))
                            {
                                if (!lRooms.Contains(sRoom) & !string.IsNullOrEmpty(sRoom))
                                {
                                    lRooms.Add(sRoom);
                                    DeviceRoom oDevRoom = new DeviceRoom();
                                    oDevRoom.Address = sAddress;
                                    oDevRoom.RoomName = sRoom;
                                    oDevRoom.Modified = DateTime.Now;

                                    try
                                    {
                                        DataContext.HMRoom.InsertOnSubmit(oDevRoom);
                                        DataContext.SubmitChanges(ConflictMode.ContinueOnConflict);
                                    }
                                    catch { }
                                }
                            }

                            //Functions abfüllen....
                            string aFunctions = sLine.Split(';')[3];
                            foreach (string sFunction in aFunctions.Split('!'))
                            {
                                string sFunc = sFunction.Replace("Verschlu�", "Verschluss");
                                if (!lFunctions.Contains(sFunc) & !string.IsNullOrEmpty(sFunc))
                                {
                                    lFunctions.Add(sFunc);
                                    DeviceFunction oDevFunc = new DeviceFunction();
                                    oDevFunc.Address = sAddress;
                                    oDevFunc.Function = sFunc;
                                    oDevFunc.Modified = DateTime.Now;

                                    try
                                    {
                                        DataContext.HMFunction.InsertOnSubmit(oDevFunc);
                                        DataContext.SubmitChanges(ConflictMode.ContinueOnConflict);
                                    }
                                    catch { }
                                }
                            }


                        }

                        if (DataContext.HMDevices.Count(t => t.Address == sAddress) > 0)
                        {
                            Device oDev = DataContext.HMDevices.Single(t => t.Address == sAddress);
                            sName = sName.Replace('�', 'ü');
                            if (string.IsNullOrEmpty(sName2))
                            {
                                oDev.DisplayName = sName;
                            }
                            else
                            {
                                oDev.DisplayName = sName + ":" + sName2;
                            }
                            //Device oDBDev = hMDB.HMDevices.Single(t => t.Address == sAddress);
                            //oDBDev.DisplayName = sName;
                            try
                            {
                                DataContext.SubmitChanges(ConflictMode.ContinueOnConflict);
                            }
                            catch { }
                        }
                        else
                        {
                            sAddress.ToString();
                        }

                    }
                    catch (Exception ex)
                    {
                        ex.Message.ToString();
                    }

                }

                HMDevices = DataContext.HMDevices.ToObservableCollection();


            }
            catch
            {
                string msg = "Die Homematic-Zentrale (CCU) ist nicht erreichbar... !\r(Hinweis: Port 2000,2001 und 8181 werden benötigt).";
                OnLoadingError(msg);
            }

            AProccessHasFinished(LoadProcesses.LoadDevices);


        }


        void AProccessHasFinished(LoadProcesses proccess)
        {
            LoadProcessesFinished.Add(proccess);
            if (LoadProcessesFinished.Contains(LoadProcesses.LoadRF) && LoadProcessesFinished.Contains(LoadProcesses.LoadWired))
                if (!LoadProcessesFinished.Contains(LoadProcesses.LoadDevices))
                    LoadDevices();
                else
                    if (!LoadProcessesFinished.Contains(LoadProcesses.LoadVariables))
                        LoadVariables();

            if (LoadProcessesFinished.Count() == 4)
                OnLoadingFinished();


        }

        private void LoadDevices()
        {
            //Load additional Details.
            WebClient oWeb = new WebClient();
            oWeb.UploadStringCompleted += new UploadStringCompletedEventHandler(oWeb_UploadStringCompleted);
            oWeb.UploadStringAsync(new Uri(@"http://" + DataContext.HMCCUSettings.Single(t => t.ConfigID == "TEST").CCU + ":8181/Test.exe"), "POST", HMQuery);

        }


        public void LoadVariables()
        {
            //Get Variables
            WebClient oGetVar = new WebClient();
            oGetVar.UploadStringCompleted += new UploadStringCompletedEventHandler(oGetVar_UploadStringCompleted);
            oGetVar.UploadStringAsync(new Uri(@"http://" + DataContext.HMCCUSettings.Single(t => t.ConfigID == "TEST").CCU + ":8181/Test.exe"), "POST", HMQueryVariables);
        }

        void oGetVar_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
        {
            try
            {
                foreach (string sLine in e.Result.Split(new string[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries))
                {
                    if (sLine.IndexOf("<xml><exec>") >= 0)
                        continue;
                    try
                    {
                        string[] aVars = sLine.Split('|');
                        sLine.ToString();
                        int ID = int.Parse(aVars[0]);
                        SystemVariable oVar = null;
                        bool bNew = true;
                        if (HMVariables.Count(t => t.ID == ID) != 0)
                        {
                            oVar = HMVariables.Single(t => t.ID == ID);
                            bNew = false;
                        }
                        if (bNew)
                        {
                            oVar = new SystemVariable();
                            oVar.ID = ID;
                        }

                        oVar.Timestamp = new DateTime(1970, 1, 1).AddSeconds(int.Parse(aVars[1]));
                        if (!DateTime.Now.IsDaylightSavingTime())
                            oVar.Timestamp = oVar.Timestamp + new TimeSpan(1, 0, 0);
                        oVar.Name = aVars[2];
                        oVar.Value = aVars[3];
                        oVar.ValueType = int.Parse(aVars[4]);
                        oVar.Subtype = int.Parse(aVars[5]);
                        string sUnit = aVars[6];
                        oVar.Unit = sUnit.Replace('�', '°');
                        oVar.Min = aVars[7];
                        oVar.Max = aVars[8];
                        oVar.Visible = bool.Parse(aVars[9]);
                        oVar.Logged = bool.Parse(aVars[10]);
                        oVar.ValueList = aVars[11];

                        if (bNew)
                        {
                            DataContext.HMVariables_.InsertOnSubmit(oVar);
                        }

                        DataContext.SubmitChanges();
                    }
                    catch (Exception ex)
                    {
                        ex.Message.ToString();
                    }
                }

                HMVariables = DataContext.HMVariables_.ToObservableCollection();

                AProccessHasFinished(LoadProcesses.LoadVariables);
            }
            catch { }
        }


        public void Dispose()
        {
            DataContext.Dispose();
            // base Dispose();

        }


    }
}
